package io.bloom.webspider;

import com.google.common.base.Strings;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Builder
@Slf4j
@Data
public class Spider {
    private String name;

    private int thread;

    private String startUrl;

    private SpiderLinkMatcher matcher;

    private Downloader downloader;

    private List<Processor> processors;

    private ExecutorService executorService;

    public void addProcessor(Processor processor) {
        if (processors == null) {
            processors = new ArrayList<Processor>();
        }
        processors.add(processor);
        Collections.sort(processors, new Comparator<Processor>() {
            public int compare(Processor o1, Processor o2) {
                return o1.index() - o2.index();
            }
        });
    }

    public void start() {
        int nThreads = getThread() == 0 ? 1 : getThread();
        //fixed Thread pool
        if (executorService == null) {
            executorService = new ThreadPoolExecutor(
                    nThreads,
                    nThreads,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>()
            );
        }
        addProcessor(Processor.console);
        execute(SpiderRequest.of(getStartUrl()));
    }

    public void execute(final SpiderRequest request) {
        SpiderWorker worker = new SpiderWorker();
        worker.setRequest(request);
        executorService.execute(worker);
    }

    public void retry(final SpiderRequest request) {
        request.incrTryCount();
        execute(request);
    }

    public void addLinks(List<String> links) {
        if (links == null) return;

        for (String link : links) {
            if (!Strings.isNullOrEmpty(link) &&
                    (matcher == null || matcher.match(link))) {
                execute(SpiderRequest.of(link));
            }
        }
    }

    @Data
    private class SpiderWorker implements Runnable {
        private SpiderRequest request;

        public void run() {
            if (null == request) return;
            SpiderResponse response = downloader.download(request);
            if (response.isFail() && request.getTryCount() < 3) {
                retry(request);
            } else if (response.isOk()) {
                Map<String, Object> context = new HashMap<String, Object>();
                addLinks(response.getLinks());
                for (Processor processor : processors) {
                    processor.execute(response, context);
                }
            }
            log.info("finished request:{}", request.getUrl());
        }
    }
}
